/*
*
*   This program is free software; you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation; either version 2 of the License, or (at
*   your option) any later version.
*
*   This program is distributed in the hope that it will be useful, but
*   WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*   General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with this program; if not, write to the Free Software Foundation,
*   Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*   In addition, as a special exception, the author gives permission to
*   link the code of this program with the Half-Life Game Engine ("HL
*   Engine") and Modified Game Libraries ("MODs") developed by Valve,
*   L.L.C ("Valve").  You must obey the GNU General Public License in all
*   respects for all of the code used other than the HL Engine and MODs
*   from Valve.  If you modify this file, you may extend this exception
*   to your version of the file, but you are not obligated to do so.  If
*   you do not wish to do so, delete this exception statement from your
*   version.
*
*/

#ifndef CS_BOT_H
#define CS_BOT_H
#ifdef _WIN32
#pragma once
#endif

#include "minmax.h"

#include "bot/cs_gamestate.h"
#include "bot/cs_bot_manager.h"
#include "bot/cs_bot_chatter.h"
#include "bot/cs_bot_statemachine.h"
#include <chrono>
#include <chrono>
#include <chrono>
#include <chrono>

#define CSBOT_VERSION_MAJOR		1
#define CSBOT_VERSION_MINOR		50

#define PRIMARY_WEAPON_BUY_COUNT	13
#define SECONDARY_WEAPON_BUY_COUNT	3

#define FLAG_PROGRESS_DRAW		0x0	// draw status bar progress
#define FLAG_PROGRESS_START		0x1	// init status bar progress
#define FLAG_PROGRESS_HIDE		0x2	// hide status bar progress

#define HI_X				0x01
#define LO_X				0x02
#define HI_Y				0x04
#define LO_Y				0x08
#define HI_Z				0x10
#define LO_Z				0x20

namespace sv {

extern int _navAreaCount;
extern int _currentIndex;
/*
extern struct BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ];
extern struct BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ];

extern struct BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ];
extern struct BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ];
*/

class BotChatterInterface;

// The Counter-strike Bot

class CCSBot: public CBot
{
public:
	CCSBot();														// constructor initializes all values to zero

public:
	int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) override;		// invoked when injured by something (EXTEND) - returns the amount of damage inflicted
	void Killed(entvars_t *pevAttacker, int iGib) override;									// invoked when killed (EXTEND)
	void RoundRespawn() override;
	void Blind(duration_t duration, duration_t holdTime, duration_t fadeTime, int alpha = 255) override;					// player blinded by a flashbang
	void OnTouchingWeapon(CWeaponBox *box) override;										// invoked when in contact with a CWeaponBox

	bool Initialize(const BotProfile *profile) override;									// (EXTEND) prepare bot for action
	void SpawnBot() override;												// (EXTEND) spawn the bot into the game

	void Upkeep() override;													// lightweight maintenance, invoked frequently
	void Update() override;													// heavyweight algorithms, invoked less often

	void Walk() override;
	void Jump() override { Jump(false); }
	bool Jump(bool mustJump = false) override;										// returns true if jump was started

	void OnBecomeZombie(ZombieLevel iEvolutionLevel) override;

	void OnEvent(GameEventType event, CBaseEntity *entity = NULL, CBaseEntity *other = NULL) override;			// invoked when event occurs in the game (some events have NULL entity)

	#define CHECK_FOV true
	bool IsVisible(const Vector *pos, bool testFOV = false) const override;							// return true if we can see the point
	bool IsVisible(CBasePlayer *player, bool testFOV = false, unsigned char *visParts = NULL) const override;		// return true if we can see any part of the player

	bool IsEnemyPartVisible(VisiblePartType part) const override;								// if enemy is visible, return the part we see for our current enemy

public:
	void Disconnect();

	// behavior properties
	float GetCombatRange() const;
	bool IsRogue() const;					// return true if we dont listen to teammates or pursue scenario goals
	void SetRogue(bool rogue);
	bool IsHurrying() const;				// return true if we are in a hurry
	void Hurry(duration_t duration);				// force bot to hurry
	bool IsSafe() const;					// return true if we are in a safe region
	bool IsWellPastSafe() const;				// return true if it is well past the early, "safe", part of the round
	bool IsEndOfSafeTime() const;				// return true if we were in the safe time last update, but not now
	duration_t GetSafeTimeRemaining() const;			// return the amount of "safe time" we have left
	duration_t GetSafeTime() const;				// return what we think the total "safe time" for this map is
	NOXREF bool IsUnhealthy() const;			// returns true if bot is low on health

	// behaviors
	void Idle();
	void Hide(CNavArea *searchFromArea = NULL, duration_t duration = -1.0s, float hideRange = 750.0f, bool holdPosition = false);						// DEPRECATED: Use TryToHide() instead
	#define USE_NEAREST true
	bool TryToHide(CNavArea *searchFromArea = NULL, EngineClock::duration duration = -1.0s, float hideRange = 750.0f, bool holdPosition = false, bool useNearest = false);			// try to hide nearby, return false if cannot

	void Hide(const Vector *hidingSpot, EngineClock::duration duration = -1.0s, bool holdPosition = false);											// move to the given hiding place
	bool IsHiding() const;																			// returns true if bot is currently hiding
	bool IsAtHidingSpot() const;																		// return true if we are hiding and at our hiding spot
	bool TryToRetreat();																			// retreat to a nearby hiding spot, away from enemies

	void Hunt();
	bool IsHunting() const;									// returns true if bot is currently hunting

	void Attack(CBaseEntity *victim);
	void FireWeaponAtEnemy();								// fire our active weapon towards our current enemy
	void StopAttacking();
	bool IsAttacking() const;								// returns true if bot is currently engaging a target

	void MoveTo(const Vector *pos, RouteType route = SAFEST_ROUTE);				// move to potentially distant position
	bool IsMovingTo() const;								// return true if we are in the MoveTo state

	void PlantBomb();

	void FetchBomb();									// bomb has been dropped - go get it
	bool NoticeLooseBomb() const;								// return true if we noticed the bomb on the ground or on radar
	bool CanSeeLooseBomb() const;								// return true if we directly see the loose bomb
	bool IsCarryingBomb() const;

	void DefuseBomb();
	bool IsDefusingBomb() const;								// returns true if bot is currently defusing the bomb
	bool CanSeePlantedBomb() const;								// return true if we directly see the planted bomb

	void EscapeFromBomb();
	bool IsEscapingFromBomb() const;							// return true if we are escaping from the bomb

	void RescueHostages();
	void UseEntity(CBaseEntity *entity);							// use the entity

	bool IsBuying() const;

	void Panic(CBasePlayer *enemy);								// look around in panic
	void Follow(CBasePlayer *player);							// begin following given Player
	void ContinueFollowing();								// continue following our leader after finishing what we were doing
	void StopFollowing();									// stop following
	bool IsFollowing() const;								// return true if we are following someone (not necessarily in the follow state)
	CBasePlayer *GetFollowLeader();								// return the leader we are following
	duration_t GetFollowDuration() const;							// return how long we've been following our leader
	bool CanAutoFollow() const;								// return true if we can auto-follow

	bool IsNotMoving() const;								// return true if we are currently standing still

	void AimAtEnemy();									// point our weapon towards our enemy
	void StopAiming();									// stop aiming at enemy
	bool IsAimingAtEnemy() const;								// returns true if we are trying to aim at an enemy

	bool IsSurprised() const;								// return true if we are "surprised"
	duration_t GetSurpriseDelay() const;
	void ClearSurpriseDelay();

	time_point_t GetStateTimestamp() const;							// get time current state was entered

	bool IsDoingScenario() const;								// return true if we will do scenario-related tasks

	// scenario / gamestate
	CSGameState *GetGameState();								// return an interface to this bot's gamestate
	const CSGameState *GetGameState() const;						// return an interface to this bot's gamestate

	bool IsAtBombsite();									// return true if we are in a bomb planting zone
	bool GuardRandomZone(float range = 500.0f);						// pick a random zone and hide near it

	bool IsBusy() const;									// return true if we are busy doing something important

	// high-level tasks
	enum TaskType
	{
		SEEK_AND_DESTROY,
		PLANT_BOMB,
		FIND_TICKING_BOMB,
		DEFUSE_BOMB,
		GUARD_TICKING_BOMB,
		GUARD_BOMB_DEFUSER,
		GUARD_LOOSE_BOMB,
		GUARD_BOMB_ZONE,
		ESCAPE_FROM_BOMB,
		HOLD_POSITION,
		FOLLOW,
		VIP_ESCAPE,
		GUARD_VIP_ESCAPE_ZONE,
		COLLECT_HOSTAGES,
		RESCUE_HOSTAGES,
		GUARD_HOSTAGES,
		GUARD_HOSTAGE_RESCUE_ZONE,
		MOVE_TO_LAST_KNOWN_ENEMY_POSITION,
		MOVE_TO_SNIPER_SPOT,
		SNIPING,

		NUM_TASKS
	};

	void SetTask(TaskType task, CBaseEntity *entity = NULL);				// set our current "task"
	TaskType GetTask() const;
	CBaseEntity *GetTaskEntity();

	// behavior modifiers
	enum DispositionType
	{
		ENGAGE_AND_INVESTIGATE,		// engage enemies on sight and investigate enemy noises
		OPPORTUNITY_FIRE,		// engage enemies on sight, but only look towards enemy noises, dont investigate
		SELF_DEFENSE,			// only engage if fired on, or very close to enemy
		IGNORE_ENEMIES,			// ignore all enemies - useful for ducking around corners, running away, etc

		NUM_DISPOSITIONS
	};

	void SetDisposition(DispositionType disposition);					// define how we react to enemies
	DispositionType GetDisposition() const;							// return enum describing current disposition

	void IgnoreEnemies(duration_t duration);							// ignore enemies for a short duration

	enum MoraleType
	{
		TERRIBLE = -3,
		BAD = -2,
		NEGATIVE = -1,
		NEUTRAL = 0,
		POSITIVE = 1,
		GOOD = 2,
		EXCELLENT = 3,
	};

	MoraleType GetMorale() const;								// return enum describing current morale
	void IncreaseMorale();
	void DecreaseMorale();

	// listening for noises
	bool IsNoiseHeard() const;								// return true if we have heard a noise
	bool ShouldInvestigateNoise(float *retNoiseDist = NULL);
	void InvestigateNoise();								// investigate recent enemy noise
	const Vector *GetNoisePosition() const;							// return position of last heard noise, or NULL if none heard
	CNavArea *GetNoiseArea() const;								// return area where noise was heard
	void ForgetNoise();									// clear the last heard noise
	bool CanSeeNoisePosition() const;							// return true if we directly see where we think the noise came from
	NOXREF float GetNoiseRange() const;							// return approximate distance to last noise heard

	bool CanHearNearbyEnemyGunfire(float range = -1.0f) const;				// return true if we hear nearby threatening enemy gunfire within given range (-1 == infinite)
	PriorityType GetNoisePriority() const;							// return priority of last heard noise

	// radio and chatter
	void SendRadioMessage(GameEventType event);						// send voice chatter
	BotChatterInterface *GetChatter();							// return an interface to this bot's chatter system
	bool RespondToHelpRequest(CBasePlayer *them, Place place, float maxRange = -1.0f);	// decide if we should move to help the player, return true if we will
	void StartVoiceFeedback(duration_t duration);
	bool IsUsingVoice() const;								// new-style "voice" chatter gets voice feedback

	// enemies
	// BOTPORT: GetEnemy() collides with GetEnemy() in CBaseEntity - need to use different nomenclature

	void SetEnemy(CBaseEntity *enemy);							// set given player as our current enemy
	CBaseEntity *GetEnemy();
	int GetNearbyEnemyCount() const;							// return max number of nearby enemies we've seen recently
	unsigned int GetEnemyPlace() const;							// return location where we see the majority of our enemies
	bool CanSeeBomber() const;								// return true if we can see the bomb carrier
	CBasePlayer *GetBomber() const;

	int GetNearbyFriendCount() const;							// return number of nearby teammates
	CBasePlayer *GetClosestVisibleFriend() const;						// return the closest friend that we can see
	CBasePlayer *GetClosestVisibleHumanFriend() const;					// return the closest human friend that we can see

	bool IsOutnumbered() const;								// return true if we are outnumbered by enemies
	int OutnumberedCount() const;								// return number of enemies we are outnumbered by

	#define ONLY_VISIBLE_ENEMIES true
	CBasePlayer *GetImportantEnemy(bool checkVisibility = false) const;			// return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)

	void UpdateReactionQueue();								// update our reaction time queue
	CBaseEntity *GetRecognizedEnemy();							// return the most dangerous threat we are "conscious" of
	bool IsRecognizedEnemyReloading();							// return true if the enemy we are "conscious" of is reloading
	bool IsRecognizedEnemyProtectedByShield();						// return true if the enemy we are "conscious" of is hiding behind a shield
	float GetRangeToNearestRecognizedEnemy();						// return distance to closest enemy we are "conscious" of

	CBasePlayer *GetAttacker() const;							// return last enemy that hurt us
	duration_t GetTimeSinceAttacked() const;							// return duration since we were last injured by an attacker
	time_point_t GetFirstSawEnemyTimestamp() const;						// time since we saw any enemies
	time_point_t GetLastSawEnemyTimestamp() const;
	duration_t GetTimeSinceLastSawEnemy() const;
	duration_t GetTimeSinceAcquiredCurrentEnemy() const;
	bool HasNotSeenEnemyForLongTime() const;						// return true if we haven't seen an enemy for "a long time"
	const Vector &GetLastKnownEnemyPosition() const;
	bool IsEnemyVisible() const;								// is our current enemy visible
	time_point_t GetEnemyDeathTimestamp() const;
	bool IsFriendInLineOfFire();								// return true if a friend is in our weapon's way
	bool IsAwareOfEnemyDeath() const;							// return true if we *noticed* that our enemy died
	int GetLastVictimID() const;								// return the ID (entindex) of the last victim we killed, or zero

	// navigation
	bool HasPath() const;
	void DestroyPath();

	float GetFeetZ() const;									// return Z of bottom of feet

	enum PathResult
	{
		PROGRESSING,	// we are moving along the path
		END_OF_PATH,	// we reached the end of the path
		PATH_FAILURE,	// we failed to reach the end of the path
	};
	#define NO_SPEED_CHANGE false
	PathResult UpdatePathMovement(bool allowSpeedChange = true);					// move along our computed path - if allowSpeedChange is true, bot will walk when near goal to ensure accuracy

	NOXREF bool AStarSearch(CNavArea *startArea, CNavArea *goalArea);				// find shortest path from startArea to goalArea - don't actually buid the path
	bool ComputePath(CNavArea *goalArea, const Vector *goal, RouteType route);			// compute path to goal position
	bool StayOnNavMesh();
	CNavArea *GetLastKnownArea() const;								// return the last area we know we were inside of
	const Vector &GetPathEndpoint() const;								// return final position of our current path
	float GetPathDistanceRemaining() const;								// eturn estimated distance left to travel along path
	void ResetStuckMonitor();
	NOXREF bool IsAreaVisible(CNavArea *area) const;						// is any portion of the area visible to this bot
	const Vector &GetPathPosition(int numpath) const;
	bool GetSimpleGroundHeightWithFloor(const Vector *pos, float *height, Vector *normal = NULL);	// find "simple" ground height, treating current nav area as part of the floor

	Place GetPlace() const;										// get our current radio chatter place

	bool IsUsingLadder() const;									// returns true if we are in the process of negotiating a ladder
	void GetOffLadder();

	void SetGoalEntity(CBaseEntity *entity);
	CBaseEntity *GetGoalEntity();

	bool IsNearJump() const;									// return true if nearing a jump in the path
	float GetApproximateFallDamage(float height) const;						// return how much damage will will take from the given fall height

	void ForceRun(duration_t duration);									// force the bot to run if it moves for the given duration
	void Wiggle();											// random movement, for getting un-stuck

	bool IsFriendInTheWay(const Vector *goalPos) const;						// return true if a friend is between us and the given position
	void FeelerReflexAdjustment(Vector *goalPosition);						// do reflex avoidance movements if our "feelers" are touched

	// looking around
	void SetLookAngles(float yaw, float pitch);			// set our desired look angles
	void UpdateLookAngles();					// move actual view angles towards desired ones
	void UpdateLookAround(bool updateNow = false);			// update "looking around" mechanism
	void InhibitLookAround(duration_t duration);				// block all "look at" and "looking around" behavior for given duration - just look ahead

	// TODO: Clean up notion of "forward angle" and "look ahead angle"
	void SetForwardAngle(float angle);				// define our forward facing
	void SetLookAheadAngle(float angle);				// define default look ahead angle

	// look at the given point in space for the given duration (-1 means forever)
	void SetLookAt(const char *desc, const Vector *pos, PriorityType pri, duration_t duration = -1.0s, bool clearIfClose = false, float angleTolerance = 5.0f);
	void ClearLookAt();						// stop looking at a point in space and just look ahead
	bool IsLookingAtSpot(PriorityType pri = PRIORITY_LOW) const;	// return true if we are looking at spot with equal or higher priority
	bool IsViewMoving(float angleVelThreshold = 1.0f) const;	// returns true if bot's view angles are rotating (not still)

	const Vector &GetEyePosition() const
	{
		m_eyePos = pev->origin + pev->view_ofs;
		return m_eyePos;
	}
	float ComputeWeaponSightRange();					// return line-of-sight distance to obstacle along weapon fire ray

	// approach points
	void ComputeApproachPoints();						// determine the set of "approach points" representing where the enemy can enter this region
	NOXREF void UpdateApproachPoints();					// recompute the approach point set if we have moved far enough to invalidate the current ones
	void ClearApproachPoints();
	void DrawApproachPoints();						// for debugging
	time_point_t GetHidingSpotCheckTimestamp(HidingSpot *spot) const;		// return time when given spot was last checked
	void SetHidingSpotCheckTimestamp(HidingSpot *spot);			// set the timestamp of the given spot to now

	// weapon query and equip
	#define MUST_EQUIP true
	void EquipBestWeapon(bool mustEquip = false);				// equip the best weapon we are carrying that has ammo
	void EquipPistol();							// equip our pistol
	void EquipKnife();							// equip our knife

	#define DONT_USE_SMOKE_GRENADE true
	bool EquipGrenade(bool noSmoke = false);				// equip a grenade, return false if we cant

	bool IsUsingKnife() const;						// returns true if we have knife equipped
	bool IsUsingPistol() const;						// returns true if we have pistol equipped
	bool IsUsingGrenade() const;						// returns true if we have grenade equipped
	bool IsUsingSniperRifle() const;					// returns true if using a "sniper" rifle
	bool IsUsingAWP() const;						// returns true if we have AWP equipped

	bool IsSniper() const;							// return true if we have a sniper rifle in our inventory
	bool IsSniping() const;							// return true if we are actively sniping (moving to sniper spot or settled in)
	bool IsUsingShotgun() const;						// returns true if using a shotgun
	bool IsUsingMachinegun() const;						// returns true if using the big 'ol machinegun
	void ThrowGrenade(const Vector *target);				// begin the process of throwing the grenade
	bool IsThrowingGrenade() const;						// return true if we are in the process of throwing a grenade
	bool HasGrenade() const;						// return true if we have a grenade in our inventory

	bool DoesActiveWeaponHaveSilencer() const;
	bool IsUsingHEGrenade() const;
	void StartRapidFire();
	void StopRapidFire();
	bool IsRapidFiring() const;

	enum ZoomType { NO_ZOOM, LOW_ZOOM, HIGH_ZOOM };
	ZoomType GetZoomLevel() const;						// return the current zoom level of our weapon

	bool AdjustZoom(float range);						// change our zoom level to be appropriate for the given range

	bool IsPrimaryWeaponEmpty() const;					// return true if primary weapon doesn't exist or is totally out of ammo
	bool IsPistolEmpty() const;						// return true if secondary weapon doesn't exist or is totally out of ammo

	int GetHostageEscortCount() const;
	void IncreaseHostageEscortCount();
	float GetRangeToFarthestEscortedHostage() const;
	void ResetWaitForHostagePatience();
	void ResetValues();							// reset internal data to initial state
	void BotDeathThink();
	CBasePlayer *FindNearbyPlayer();
	void AdjustSafeTime();							// called when enemy seen to adjust safe time for this round
	void EXPORT BotTouch(CBaseEntity *other);
	bool HasAnyAmmo(CBasePlayerWeapon *weapon) const;

private:
	friend class CCSBotManager;

	// TODO: Get rid of these
	friend class AttackState;
	friend class BuyState;

	char m_name[64];					// copied from STRING(pev->netname) for debugging

	// behavior properties
	float m_combatRange;					// desired distance between us and them during gunplay
	mutable bool m_isRogue;					// if true, the bot is a "rogue" and listens to no-one
	mutable CountdownTimer m_rogueTimer;
	MoraleType m_morale;					// our current morale, based on our win/loss history
	bool m_diedLastRound;					// true if we died last round
	duration_t m_safeTime;					// duration at the beginning of the round where we feel "safe"
	bool m_wasSafe;						// true if we were in the safe time last update
	NavRelativeDirType m_blindMoveDir;			// which way to move when we're blind
	bool m_blindFire;					// if true, fire weapon while blinded

	// TODO: implement through CountdownTimer
	duration_t m_surpriseDelay;					// when we were surprised
	time_point_t m_surpriseTimestamp;

	bool m_isFollowing;					// true if we are following someone
	EntityHandle<CBasePlayer> m_leader;					// the ID of who we are following
	time_point_t m_followTimestamp;				// when we started following
	time_point_t m_allowAutoFollowTime;				// time when we can auto follow

	CountdownTimer m_hurryTimer;				// if valid, bot is in a hurry

	// instances of each possible behavior state, to avoid dynamic memory allocation during runtime
	IdleState m_idleState;
	HuntState m_huntState;
	AttackState m_attackState;
	InvestigateNoiseState m_investigateNoiseState;
	BuyState m_buyState;
	MoveToState m_moveToState;
	FetchBombState m_fetchBombState;
	PlantBombState m_plantBombState;
	DefuseBombState m_defuseBombState;
	HideState m_hideState;
	EscapeFromBombState m_escapeFromBombState;
	FollowState m_followState;
	UseEntityState m_useEntityState;

	// TODO: Allow multiple simultaneous state machines (look around, etc)
	void SetState(BotState *state);						// set the current behavior state
	BotState *m_state;							// current behavior state
	time_point_t m_stateTimestamp;							// time state was entered
	bool m_isAttacking;							// if true, special Attack state is overriding the state machine

	TaskType m_task;							// our current task
	EHANDLE m_taskEntity;							// an entity used for our task

	// navigation
	Vector m_goalPosition;
	EHANDLE m_goalEntity;
	void MoveTowardsPosition(const Vector *pos);				// move towards position, independant of view angle
	NOXREF void MoveAwayFromPosition(const Vector *pos);			// move away from position, independant of view angle
	void StrafeAwayFromPosition(const Vector *pos);				// strafe (sidestep) away from position, independant of view angle
	void StuckCheck();							// check if we have become stuck
	bool BhopJump_Start();
	void BhopJump_UpdateJump();
	void BhopJump_UpdateSync();
	bool Knockback(CBasePlayer *attacker, const KnockbackData &data) override; // knockback revolves

	CNavArea *m_currentArea;						// the nav area we are standing on
	CNavArea *m_lastKnownArea;						// the last area we were in
	EHANDLE m_avoid;							// higher priority player we need to make way for
	time_point_t m_avoidTimestamp;
	bool m_isJumpCrouching;
	bool m_isJumpCrouched;
	bool m_isBhopJumping;
	time_point_t m_flBhopSyncNext;
	NavRelativeDirType m_BhopLastSyncDir;
	time_point_t m_jumpCrouchTimestamp;

	// path navigation data
	enum { 	MAX_PATH_LENGTH = 256 };
	struct ConnectInfo
	{
		CNavArea *area;			// the area along the path
		NavTraverseType how;		// how to enter this area from the previous one
		Vector pos;			// our movement goal position at this point in the path
		const CNavLadder *ladder;	// if "how" refers to a ladder, this is it
	}
	m_path[ MAX_PATH_LENGTH ];
	int m_pathLength;
	int m_pathIndex;
	time_point_t m_areaEnteredTimestamp;
	void BuildTrivialPath(const Vector *goal);										// build trivial path to goal, assuming we are already in the same area
	bool FindGrenadeTossPathTarget(Vector *pos);

	CountdownTimer m_repathTimer;												// must have elapsed before bot can pathfind again

	bool ComputePathPositions();												// determine actual path positions bot will move between along the path
	void SetupLadderMovement();
	void SetPathIndex(int newIndex);											// set the current index along the path
	void DrawPath();
	int FindOurPositionOnPath(Vector *close, bool local = false) const;							// compute the closest point to our current position on our path
	int FindPathPoint(float aheadRange, Vector *point, int *prevIndex = NULL);						// compute a point a fixed distance ahead along our path.
	bool FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const;			// compute closest point on path to given point
	bool IsStraightLinePathWalkable(const Vector *goal) const;								// test for un-jumpable height change, or unrecoverable fall

	mutable CountdownTimer m_avoidFriendTimer;		// used to throttle how often we check for friends in our path
	mutable bool m_isFriendInTheWay;			// true if a friend is blocking our path
	CountdownTimer m_politeTimer;				// we'll wait for friend to move until this runs out
	bool m_isWaitingBehindFriend;				// true if we are waiting for a friend to move

	#define ONLY_JUMP_DOWN true
	bool DiscontinuityJump(float ground, bool onlyJumpDown = false, bool mustJump = false);					// check if we need to jump due to height change

	enum LadderNavState
	{
		APPROACH_ASCENDING_LADDER,		// prepare to scale a ladder
		APPROACH_DESCENDING_LADDER,		// prepare to go down ladder
		FACE_ASCENDING_LADDER,
		FACE_DESCENDING_LADDER,
		MOUNT_ASCENDING_LADDER,			// move toward ladder until "on" it
		MOUNT_DESCENDING_LADDER,		// move toward ladder until "on" it
		ASCEND_LADDER,				// go up the ladder
		DESCEND_LADDER,				// go down the ladder
		DISMOUNT_ASCENDING_LADDER,		// get off of the ladder
		DISMOUNT_DESCENDING_LADDER,		// get off of the ladder
		MOVE_TO_DESTINATION,			// dismount ladder and move to destination area
	}
	m_pathLadderState;
	bool m_pathLadderFaceIn;			// if true, face towards ladder, otherwise face away
	const CNavLadder *m_pathLadder;			// the ladder we need to use to reach the next area
	bool UpdateLadderMovement();			// called by UpdatePathMovement()
	NavRelativeDirType m_pathLadderDismountDir;	// which way to dismount
	time_point_t m_pathLadderDismountTimestamp;		// time when dismount started
	float m_pathLadderEnd;				// if ascending, z of top, if descending z of bottom
	void ComputeLadderEndpoint(bool isAscending);
	time_point_t m_pathLadderTimestamp;			// time when we started using ladder - for timeout check

	CountdownTimer m_mustRunTimer;			// if nonzero, bot cannot walk

	// game scenario mechanisms
	CSGameState m_gameState;

	// hostages mechanism
	byte m_hostageEscortCount;
	void UpdateHostageEscortCount();
	time_point_t m_hostageEscortCountTimestamp;
	bool m_isWaitingForHostage;
	CountdownTimer m_inhibitWaitingForHostageTimer;
	CountdownTimer m_waitForHostageTimer;

	// listening mechanism
	Vector m_noisePosition;				// position we last heard non-friendly noise
	time_point_t m_noiseTimestamp;				// when we heard it (can get zeroed)
	CNavArea *m_noiseArea;				// the nav area containing the noise
	time_point_t m_noiseCheckTimestamp;
	PriorityType m_noisePriority;			// priority of currently heard noise
	bool UpdateLookAtNoise();			// return true if we decided to look towards the most recent noise source
	bool m_isNoiseTravelRangeChecked;

	// "looking around" mechanism
	time_point_t m_lookAroundStateTimestamp;		// time of next state change
	float m_lookAheadAngle;				// our desired forward look angle
	float m_forwardAngle;				// our current forward facing direction
	time_point_t m_inhibitLookAroundTimestamp;		// time when we can look around again

	enum LookAtSpotState
	{
		NOT_LOOKING_AT_SPOT,		// not currently looking at a point in space
		LOOK_TOWARDS_SPOT,		// in the process of aiming at m_lookAtSpot
		LOOK_AT_SPOT,			// looking at m_lookAtSpot
		NUM_LOOK_AT_SPOT_STATES
	}
	m_lookAtSpotState;
	Vector m_lookAtSpot;					// the spot we're currently looking at
	PriorityType m_lookAtSpotPriority;
	duration_t m_lookAtSpotDuration;				// how long we need to look at the spot
	time_point_t m_lookAtSpotTimestamp;				// when we actually began looking at the spot
	float m_lookAtSpotAngleTolerance;			// how exactly we must look at the spot
	bool m_lookAtSpotClearIfClose;				// if true, the look at spot is cleared if it gets close to us
	const char *m_lookAtDesc;				// for debugging
	void UpdateLookAt();
	void UpdatePeripheralVision();				// update enounter spot timestamps, etc
	time_point_t m_peripheralTimestamp;

	enum { MAX_APPROACH_POINTS = 16 };
	Vector m_approachPoint[ MAX_APPROACH_POINTS ];
	unsigned char m_approachPointCount;
	Vector m_approachPointViewPosition;			// the position used when computing current approachPoint set

	bool BendLineOfSight(const Vector *eye, const Vector *point, Vector *bend) const;		// "bend" our line of sight until we can see the target point. Return bend point, false if cant bend.
	NOXREF bool FindApproachPointNearestPath(Vector *pos);						// find the approach point that is nearest to our current path, ahead of us

	bool m_isWaitingToTossGrenade;				// lining up throw
	CountdownTimer m_tossGrenadeTimer;			// timeout timer for grenade tossing

	SpotEncounter *m_spotEncounter;				// the spots we will encounter as we move thru our current area
	time_point_t m_spotCheckTimestamp;				// when to check next encounter spot

	// TODO: Add timestamp for each possible client to hiding spots
	enum { MAX_CHECKED_SPOTS = 64 };
	struct HidingSpotCheckInfo
	{
		HidingSpot *spot;
		time_point_t timestamp;
	}
	m_checkedHidingSpot[ MAX_CHECKED_SPOTS ];
	int m_checkedHidingSpotCount;

	// view angle mechanism
	float m_lookPitch;					// our desired look pitch angle
	float m_lookPitchVel;
	float m_lookYaw;					// our desired look yaw angle
	float m_lookYawVel;

	// aim angle mechanism
	mutable Vector m_eyePos;
	Vector m_aimOffset;					// current error added to victim's position to get actual aim spot
	Vector m_aimOffsetGoal;					// desired aim offset
	time_point_t m_aimOffsetTimestamp;				// time of next offset adjustment
	time_point_t m_aimSpreadTimestamp;				// time used to determine max spread as it begins to tighten up
	void SetAimOffset(float accuracy);			// set the current aim offset
	void UpdateAimOffset();					// wiggle aim error based on m_accuracy
	Vector m_aimSpot;					// the spot we are currently aiming to fire at

	// attack state data
	DispositionType m_disposition;				// how we will react to enemies
	CountdownTimer m_ignoreEnemiesTimer;			// how long will we ignore enemies
	mutable EHANDLE m_enemy;				// our current enemy
	bool m_isEnemyVisible;					// result of last visibility test on enemy
	unsigned char m_visibleEnemyParts;			// which parts of the visible enemy do we see
	Vector m_lastEnemyPosition;				// last place we saw the enemy
	time_point_t m_lastSawEnemyTimestamp;
	time_point_t m_firstSawEnemyTimestamp;
	time_point_t m_currentEnemyAcquireTimestamp;
	time_point_t m_enemyDeathTimestamp;				// if m_enemy is dead, this is when he died
	bool m_isLastEnemyDead;					// true if we killed or saw our last enemy die
	int m_nearbyEnemyCount;					// max number of enemies we've seen recently
	unsigned int m_enemyPlace;				// the location where we saw most of our enemies

	struct WatchInfo
	{
		time_point_t timestamp;
		bool isEnemy;
	}
	m_watchInfo[ MAX_CLIENTS ];
	mutable EntityHandle<CBasePlayer> m_bomber;				// points to bomber if we can see him

	int m_nearbyFriendCount;				// number of nearby teammates
	mutable EntityHandle<CBasePlayer> m_closestVisibleFriend;			// the closest friend we can see
	mutable EntityHandle<CBasePlayer> m_closestVisibleHumanFriend;		// the closest human friend we can see

	CBasePlayer *m_attacker;				// last enemy that hurt us (may not be same as m_enemy)
	time_point_t m_attackedTimestamp;				// when we were hurt by the m_attacker

	int m_lastVictimID;					// the entindex of the last victim we killed, or zero
	bool m_isAimingAtEnemy;					// if true, we are trying to aim at our enemy
	bool m_isRapidFiring;					// if true, RunUpkeep() will toggle our primary attack as fast as it can
	IntervalTimer m_equipTimer;				// how long have we had our current weapon equipped
	bool DoEquip(CBasePlayerWeapon *gun);			// equip the given item

	void ReloadCheck();					// reload our weapon if we must
	void SilencerCheck();					// use silencer

	time_point_t m_fireWeaponTimestamp;

	// reaction time system
	enum { MAX_ENEMY_QUEUE = 20 };
	struct ReactionState
	{
		// NOTE: player position & orientation is not currently stored separately
		EHANDLE player;
		bool isReloading;
		bool isProtectedByShield;
	}
	m_enemyQueue[ MAX_ENEMY_QUEUE ];			// round-robin queue for simulating reaction times

	byte m_enemyQueueIndex;
	byte m_enemyQueueCount;
	byte m_enemyQueueAttendIndex;				// index of the timeframe we are "conscious" of

	CBasePlayer *FindMostDangerousThreat();			// return most dangerous threat in my field of view (feeds into reaction time queue)

	// stuck detection
	bool m_isStuck;
	time_point_t m_stuckTimestamp;					// time when we got stuck
	Vector m_stuckSpot;					// the location where we became stuck
	NavRelativeDirType m_wiggleDirection;
	time_point_t m_wiggleTimestamp;
	time_point_t m_stuckJumpTimestamp;				// time for next jump when stuck

	enum { MAX_VEL_SAMPLES = 5 };
	float m_avgVel[ MAX_VEL_SAMPLES ];
	int m_avgVelIndex;
	int m_avgVelCount;
	Vector m_lastOrigin;

	// chatter mechanism
	GameEventType m_lastRadioCommand;			// last radio command we recieved
	void RespondToRadioCommands();
	bool IsRadioCommand(GameEventType event) const;		// returns true if the radio message is an order to do something

	#define NO_FORCE false
	void EndVoiceFeedback(bool force = true);
	time_point_t m_lastRadioRecievedTimestamp;			// time we recieved a radio message
	time_point_t m_lastRadioSentTimestamp;				// time when we send a radio message
	EntityHandle<CBasePlayer> m_radioSubject;					// who issued the radio message
	Vector m_radioPosition;					// position referred to in radio message
	time_point_t m_voiceFeedbackStartTimestamp;
	time_point_t m_voiceFeedbackEndTimestamp;			// new-style "voice" chatter gets voice feedback
	BotChatterInterface m_chatter;

	// learn map mechanism
	const CNavNode *m_navNodeList;
	CNavNode *m_currentNode;
	NavDirType m_generationDir;
	NavAreaList::iterator m_analyzeIter;

	enum ProcessType
	{
		PROCESS_NORMAL,
		PROCESS_LEARN,
		PROCESS_ANALYZE_ALPHA,
		PROCESS_ANALYZE_BETA,
		PROCESS_SAVE,
	}
	m_processMode;
	CountdownTimer m_mumbleTimer;
	CountdownTimer m_booTimer;
	CountdownTimer m_relocateTimer;

	CNavNode *AddNode(const Vector *destPos, const Vector *normal, NavDirType dir, CNavNode *source);
	void StartLearnProcess();
	void UpdateLearnProcess();
	bool LearnStep();
	void StartAnalyzeAlphaProcess();
	void UpdateAnalyzeAlphaProcess();
	bool AnalyzeAlphaStep();
	void StartAnalyzeBetaProcess();
	void UpdateAnalyzeBetaProcess();
	bool AnalyzeBetaStep();
	void StartSaveProcess();
	void UpdateSaveProcess();
	void StartNormalProcess();
};

// Inlines

inline float CCSBot::GetCombatRange() const
{
	return m_combatRange;
}

inline void CCSBot::SetRogue(bool rogue)
{
	m_isRogue = rogue;
}

inline void CCSBot::Hurry(duration_t duration)
{
	m_hurryTimer.Start(duration);
}

inline duration_t CCSBot::GetSafeTime() const
{
	return m_safeTime;
}

inline bool CCSBot::IsCarryingBomb() const
{
	return m_bHasC4;
}

inline bool CCSBot::IsFollowing() const
{
	return m_isFollowing;
}

inline CBasePlayer *CCSBot::GetFollowLeader()
{
	return static_cast<CBasePlayer *>(m_leader);
}

inline duration_t CCSBot::GetFollowDuration() const
{
	return gpGlobals->time - m_followTimestamp;
}

inline bool CCSBot::CanAutoFollow() const
{
	return (gpGlobals->time > m_allowAutoFollowTime);
}

inline void CCSBot::AimAtEnemy()
{
	m_isAimingAtEnemy = true;
}

inline void CCSBot::StopAiming()
{
	m_isAimingAtEnemy = false;
}

inline bool CCSBot::IsAimingAtEnemy() const
{
	return m_isAimingAtEnemy;
}

inline bool CCSBot::IsSurprised() const
{
	return gpGlobals->time - m_surpriseTimestamp < 5.0s;
}

inline duration_t CCSBot::GetSurpriseDelay() const
{
	if (!IsSurprised())
		return 0.0s;

	return m_surpriseDelay;
}

inline void CCSBot::ClearSurpriseDelay()
{
	m_surpriseDelay = 0.0s;
	m_surpriseTimestamp = invalid_time_point;
}

inline time_point_t CCSBot::GetStateTimestamp() const
{
	return m_stateTimestamp;
}

inline CSGameState *CCSBot::GetGameState()
{
	return &m_gameState;
}

inline const CSGameState *CCSBot::GetGameState() const
{
	return &m_gameState;
}

inline bool CCSBot::IsAtBombsite()
{
	return (m_signals.GetState() & SIGNAL_BOMB) == SIGNAL_BOMB;
}

inline CCSBot::MoraleType CCSBot::GetMorale() const
{
	return m_morale;
}

inline bool CCSBot::IsNoiseHeard() const
{
	if (m_noiseTimestamp <= invalid_time_point)
		return false;

	// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
	if (gpGlobals->time - m_noiseTimestamp >= GetProfile()->GetReactionTime())
		return true;

	return false;
}

inline void CCSBot::SetTask(TaskType task, CBaseEntity *entity)
{
	m_task = task;
	m_taskEntity = entity;
}

inline CCSBot::TaskType CCSBot::GetTask() const
{
	return m_task;
}

inline CBaseEntity *CCSBot::GetTaskEntity()
{
	return m_taskEntity;
}

inline CNavArea *CCSBot::GetNoiseArea() const
{
	return m_noiseArea;
}

inline void CCSBot::ForgetNoise()
{
	m_noiseTimestamp = invalid_time_point;
}

inline PriorityType CCSBot::GetNoisePriority() const
{
	return m_noisePriority;
}

inline BotChatterInterface *CCSBot::GetChatter()
{
	return &m_chatter;
}

inline bool CCSBot::IsUsingVoice() const
{
	return (m_voiceFeedbackEndTimestamp != invalid_time_point);
}

inline CBaseEntity *CCSBot::GetEnemy()
{
	return m_enemy;
}

inline int CCSBot::GetNearbyEnemyCount() const
{
	return min(GetEnemiesRemaining(), m_nearbyEnemyCount);
}

inline unsigned int CCSBot::GetEnemyPlace() const
{
	return m_enemyPlace;
}

inline bool CCSBot::CanSeeBomber() const
{
	return ((CBaseEntity *)m_bomber == nullptr) ? false : true;
}

inline CBasePlayer *CCSBot::GetBomber() const
{
	return static_cast<CBasePlayer *>(m_bomber);
}

inline int CCSBot::GetNearbyFriendCount() const
{
	return min(GetFriendsRemaining(), m_nearbyFriendCount);
}

inline CBasePlayer *CCSBot::GetClosestVisibleFriend() const
{
	return static_cast<CBasePlayer *>(m_closestVisibleFriend);
}

inline CBasePlayer *CCSBot::GetClosestVisibleHumanFriend() const
{
	return static_cast<CBasePlayer *>(m_closestVisibleHumanFriend);
}

inline duration_t CCSBot::GetTimeSinceAttacked() const
{
	return gpGlobals->time - m_attackedTimestamp;
}

inline time_point_t CCSBot::GetFirstSawEnemyTimestamp() const
{
	return m_firstSawEnemyTimestamp;
}

inline time_point_t CCSBot::GetLastSawEnemyTimestamp() const
{
	return m_lastSawEnemyTimestamp;
}

inline duration_t CCSBot::GetTimeSinceLastSawEnemy() const
{
	return gpGlobals->time - m_lastSawEnemyTimestamp;
}

inline duration_t CCSBot::GetTimeSinceAcquiredCurrentEnemy() const
{
	return gpGlobals->time - m_currentEnemyAcquireTimestamp;
}

inline const Vector &CCSBot::GetLastKnownEnemyPosition() const
{
	return m_lastEnemyPosition;
}

inline bool CCSBot::IsEnemyVisible() const
{
	return m_isEnemyVisible;
}

inline time_point_t CCSBot::GetEnemyDeathTimestamp() const
{
	return m_enemyDeathTimestamp;
}

inline int CCSBot::GetLastVictimID() const
{
	return m_lastVictimID;
}

inline bool CCSBot::HasPath() const
{
	return m_pathLength != 0;
}

inline void CCSBot::DestroyPath()
{
	m_pathLength = 0;
	m_pathLadder = NULL;
}

inline CNavArea *CCSBot::GetLastKnownArea() const
{
	return m_lastKnownArea;
}

inline const Vector &CCSBot::GetPathEndpoint() const
{
	return m_path[ m_pathLength - 1 ].pos;
}

inline const Vector &CCSBot::GetPathPosition(int numpath) const
{
	return m_path[ numpath ].pos;
}

inline bool CCSBot::IsUsingLadder() const
{
	return m_pathLadder != NULL;
}

inline void CCSBot::SetGoalEntity(CBaseEntity *entity)
{
	m_goalEntity = entity;
}

inline CBaseEntity *CCSBot::GetGoalEntity()
{
	return m_goalEntity;
}

inline void CCSBot::ForceRun(duration_t duration)
{
	Run();
	m_mustRunTimer.Start(duration);
}

inline void CCSBot::SetLookAngles(float yaw, float pitch)
{
	m_lookYaw = yaw;
	m_lookPitch = pitch;
}

inline void CCSBot::SetForwardAngle(float angle)
{
	m_forwardAngle = angle;
}

inline void CCSBot::SetLookAheadAngle(float angle)
{
	m_lookAheadAngle = angle;
}

inline void CCSBot::ClearLookAt()
{
	//PrintIfWatched("ClearLookAt()\n");
	m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
	m_lookAtDesc = NULL;
}

inline bool CCSBot::IsLookingAtSpot(PriorityType pri) const
{
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority >= pri)
		return true;

	return false;
}

inline bool CCSBot::IsViewMoving(float angleVelThreshold) const
{
	if (m_lookYawVel < angleVelThreshold && m_lookYawVel > -angleVelThreshold &&
		m_lookPitchVel < angleVelThreshold && m_lookPitchVel > -angleVelThreshold)
	{
		return false;
	}
	return true;
}

inline void CCSBot::ClearApproachPoints()
{
	m_approachPointCount = 0;
}

inline bool CCSBot::IsThrowingGrenade() const
{
	return m_isWaitingToTossGrenade;
}

inline void CCSBot::StartRapidFire()
{
	m_isRapidFiring = true;
}

inline void CCSBot::StopRapidFire()
{
	m_isRapidFiring = false;
}

inline CCSBot::ZoomType CCSBot::GetZoomLevel() const
{
	if (m_iFOV > 60.0f)
		return NO_ZOOM;

	if (m_iFOV > 25.0f)
		return LOW_ZOOM;

	return HIGH_ZOOM;
}

inline int CCSBot::GetHostageEscortCount() const
{
	return m_hostageEscortCount;
}

inline void CCSBot::IncreaseHostageEscortCount()
{
	++m_hostageEscortCount;
}

inline void CCSBot::ResetWaitForHostagePatience()
{
	m_isWaitingForHostage = false;
	m_inhibitWaitingForHostageTimer.Invalidate();
}

inline float CCSBot::GetFeetZ() const
{
	if (IsCrouching())
	{
		const Vector crouch = Vector(0, 0, -StepHeight);
		return (pev->origin + crouch).z;
	}
	else
	{
		const Vector stand = Vector(0, 0, -HalfHumanHeight);
		return (pev->origin + stand).z;
	}
}

inline const Vector *CCSBot::GetNoisePosition() const
{
	if (m_noiseTimestamp > invalid_time_point)
		return &m_noisePosition;

	return NULL;
}

inline bool CCSBot::IsNotMoving() const
{
	const float stillSpeed = 10.0f;
	return pev->velocity.IsLengthLessThan(stillSpeed);
}

inline bool CCSBot::HasAnyAmmo(CBasePlayerWeapon *weapon) const
{
	return (weapon->m_iClip != 0 || m_rgAmmo[weapon->m_iPrimaryAmmoType] > 0);
}

class CollectRetreatSpotsFunctor
{
public:
	CollectRetreatSpotsFunctor(CCSBot *me, float range)
	{
		m_me = me;
		m_count = 0;
		m_range = range;
	}

	enum { MAX_SPOTS = 256 };

	bool operator()(CNavArea *area)
	{
		// collect all the hiding spots in this area
		for (auto const spot : *area->GetHidingSpotList())
		{

			if (m_count >= MAX_SPOTS)
				break;

			// make sure hiding spot is in range
			if (m_range > 0.0f)
			{
				if ((*spot->GetPosition() - m_me->pev->origin).IsLengthGreaterThan(m_range))
					continue;
			}

			// if a Player is using this hiding spot, don't consider it
			if (IsSpotOccupied(m_me, spot->GetPosition()))
			{
				// player is in hiding spot
				// TODO: Check if player is moving or sitting still
				continue;
			}

			// don't select spot if an enemy can see it
			if (UTIL_IsVisibleToTeam(*spot->GetPosition() + Vector(0, 0, HalfHumanHeight), OtherTeam(m_me->m_iTeam)))
				continue;

			// don't select spot if it is closest to an enemy
			CBasePlayer *owner = UTIL_GetClosestPlayer(spot->GetPosition());
			if (owner != NULL && m_me->m_iTeam != owner->m_iTeam)
				continue;

			m_spot[ m_count++ ] = spot->GetPosition();
		}

		// if we've filled up, stop searching
		if (m_count == MAX_SPOTS)
			return false;

		return true;
	}

	CCSBot *m_me;
	float m_range;

	const Vector *m_spot[ MAX_SPOTS ];
	int m_count;
};

class FarthestHostage
{
public:
	FarthestHostage(const CCSBot *me)
	{
		m_me = me;
		m_farRange = -1.0f;
	}
	bool operator()(CHostage *hostage)
	{
		if (hostage->IsFollowing(m_me))
		{
			float range = (hostage->Center() - m_me->pev->origin).Length();

			if (range > m_farRange)
			{
				m_farRange = range;
			}
		}

		return true;

		/*if (hostage->pev->takedamage != DAMAGE_YES)
			return true;

		if (hostage->m_improv != NULL)
		{
			if (!hostage->IsFollowingSomeone() || m_me != hostage->GetLeader())
				return true;
		}
		else if (!hostage->IsFollowing(m_me))
			return true;

		float range = (hostage->Center() - m_me->pev->origin).Length();

		if (range > m_farRange)
		{
			m_farRange = range;
		}

		return true;*/
	}

	const CCSBot *m_me;
	float m_farRange;
};

// Functor used with NavAreaBuildPath()

class PathCost
{
public:
	PathCost(CCSBot *bot, RouteType route = SAFEST_ROUTE)
	{
		m_bot = bot;
		m_route = route;
	}
	float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
	{
		const float baseDangerFactor = 100.0f;

		// respond to the danger modulated by our aggression (even super-aggressives pay SOME attention to danger)
		float dangerFactor = (1.0f - (0.95f * m_bot->GetProfile()->GetAggression())) * baseDangerFactor;

		if (fromArea == NULL)
		{
			if (m_route == FASTEST_ROUTE)
				return 0.0f;

			// first area in path, cost is just danger
			return dangerFactor * area->GetDanger(m_bot->m_iTeam - 1);
		}
		else if ((fromArea->GetAttributes() & NAV_JUMP) && (area->GetAttributes() & NAV_JUMP))
		{
			// cannot actually walk in jump areas - disallow moving from jump area to jump area
			return -1.0f;
		}
		else
		{
			// compute distance from previous area to this area
			float dist;
			if (ladder)
			{
				// ladders are slow to use
				const float ladderPenalty = 1.0f;
				dist = ladderPenalty * ladder->m_length;

				// if we are currently escorting hostages, avoid ladders (hostages are confused by them)
				//if (m_bot->GetHostageEscortCount())
				//	dist *= 100.0f;
			}
			else
			{
				dist = (*area->GetCenter() - *fromArea->GetCenter()).Length();
			}

			// compute distance travelled along path so far
			float cost = dist + fromArea->GetCostSoFar();

			// zombies ignore all path penalties
			if (cv_bot_zombie.value > 0.0f)
				return cost;

			// add cost of "jump down" pain unless we're jumping into water
			if (!area->IsConnected(fromArea, NUM_DIRECTIONS))
			{
				// this is a "jump down" (one way drop) transition - estimate damage we will take to traverse it
				float fallDistance = -fromArea->ComputeHeightChange(area);

				// if it's a drop-down ladder, estimate height from the bottom of the ladder to the lower area
				//if (ladder && ladder->m_bottom.z < fromArea->GetCenter()->z && ladder->m_bottom.z > area->GetCenter()->z)
				//{
				//	fallDistance = ladder->m_bottom.z - area->GetCenter()->z;
				//}

				float fallDamage = m_bot->GetApproximateFallDamage(fallDistance);

				if (fallDamage > 0.0f)
				{
					// if the fall would kill us, don't use it
					const float deathFallMargin = 10.0f;
					if (fallDamage + deathFallMargin >= m_bot->pev->health)
						return -1.0f;

					// if we need to get there in a hurry, ignore minor pain
					const float painTolerance = 15.0f * m_bot->GetProfile()->GetAggression() + 10.0f;
					if (m_route != FASTEST_ROUTE || fallDamage > painTolerance)
					{
						// cost is proportional to how much it hurts when we fall
						// 10 points - not a big deal, 50 points - ouch!
						cost += 100.0f * fallDamage * fallDamage;
					}
				}
			}

			// if this is a "crouch" area, add penalty
			if (area->GetAttributes() & NAV_CROUCH)
			{
				// these areas are very slow to move through
				float crouchPenalty = (m_route == FASTEST_ROUTE) ? 20.0f : 5.0f;

				// avoid crouch areas if we are rescuing hostages
				if (m_bot->GetHostageEscortCount())
				{
					crouchPenalty *= 3.0f;
				}

				cost += crouchPenalty * dist;
			}

			// if this is a "jump" area, add penalty
			if (area->GetAttributes() & NAV_JUMP)
			{
				// jumping can slow you down
				//const float jumpPenalty = (m_route == FASTEST_ROUTE) ? 100.0f : 0.5f;
				const float jumpPenalty = 1.0f;
				cost += jumpPenalty * dist;
			}

			if (m_route == SAFEST_ROUTE)
			{
				// add in the danger of this path - danger is per unit length travelled
				cost += dist * dangerFactor * area->GetDanger(m_bot->m_iTeam - 1);
			}

			if (!m_bot->IsAttacking())
			{
				// add in cost of teammates in the way
				// approximate density of teammates based on area
				float size = (area->GetSizeX() + area->GetSizeY()) / 2.0f;

				// degenerate check
				if (size >= 1.0f)
				{
					// cost is proportional to the density of teammates in this area
					const float costPerFriendPerUnit = 50000.0f;
					cost += costPerFriendPerUnit * (float)area->GetPlayerCount(m_bot->m_iTeam, m_bot) / size;
				}
			}

			return cost;
		}

		return 0.0f;
	}

private:
	CCSBot *m_bot;
	RouteType m_route;
};

class FollowTargetCollector
{
public:
	FollowTargetCollector(CBasePlayer *player)
	{
		m_player = player;
		m_forward.x = player->pev->velocity.x;
		m_forward.y = player->pev->velocity.y;

		float speed = m_forward.NormalizeInPlace();

		const float walkSpeed = 100.0f;
		if (speed < walkSpeed)
		{
			m_cutoff.x = player->pev->origin.x;
			m_cutoff.y = player->pev->origin.y;

			m_forward.x = 0.0f;
			m_forward.y = 0.0f;
		}
		else
		{
			const float k = 1.5f;
			float trimSpeed = (speed < 200.0f) ? speed : 200.0f;

			m_cutoff.x = player->pev->origin.x + k * trimSpeed * m_forward.x;
			m_cutoff.y = player->pev->origin.y + k * trimSpeed * m_forward.y;
		}

		m_targetAreaCount = 0;
	}

	enum { MAX_TARGET_AREAS = 128 };

	/* <568dc3> ../cstrike/dlls/bot/states/cs_bot_follow.cpp:124 */
	bool operator()(CNavArea *area)
	{
		if (m_targetAreaCount >= MAX_TARGET_AREAS)
			return false;

		// only use two-way connections
		if (!area->GetParent() || area->IsConnected(area->GetParent(), NUM_DIRECTIONS))
		{
			if (m_forward.IsZero(0.01f))
			{
				m_targetArea[ m_targetAreaCount++ ] = area;
			}
			else
			{
				// collect areas in the direction of the player's forward motion
				Vector2D to(((*area->GetCenter()).x - m_cutoff.x), (*area->GetCenter()).y - m_cutoff.y);
				to.NormalizeInPlace();

				//if (DotProduct(to, m_forward) > 0.7071f)
				if ((to.x * m_forward.x + to.y * m_forward.y) > 0.7071f)
					m_targetArea[ m_targetAreaCount++ ] = area;
			}
		}

		return (m_targetAreaCount < MAX_TARGET_AREAS);
	}

	CBasePlayer *m_player;
	Vector2D m_forward;
	Vector2D m_cutoff;
	CNavArea *m_targetArea[ MAX_TARGET_AREAS ];
	int m_targetAreaCount;
};

void InstallBotControl();
void Bot_ServerCommand();
void Bot_RegisterCvars();
int GetBotFollowCount(CBasePlayer *leader);
const Vector *FindNearbyRetreatSpot(CCSBot *me, float maxRange);

void drawProgressMeter(float progress, char *title);
void startProgressMeter(const char *title);
void hideProgressMeter();

bool isSniperRifle(CBasePlayerItem *item);
float StayOnLadderLine(CCSBot *me, const CNavLadder *ladder);

}

#endif // CS_BOT_H
